from selenium import webdriver
from selenium.common import NoSuchElementException
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import logging
import re
# 日志格式化
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
'''配置基础属性，URL，爬取数量，浏览器驱动，已爬取的电影列表等'''
# URL
BASE_URL = 'https://spa3.scrape.center/'
# 最大数
MAX_NUMBER = 100
# 已爬取列表
scraped_titles = []
# 浏览器驱动路径
driver_path = 'D:\chromedriver-win64\chromedriver.exe'
# 为了增添无头模式
options = Options()
# options.add_argument('--headless')
# 初始化谷歌浏览器
browser = webdriver.Chrome(service=Service(driver_path), options=options)
# 隐式等待
browser.implicitly_wait(3)
# 超时设置
wait = WebDriverWait(browser, 10)

'''封装获取首页展示的所有电影数据函数'''
def scrape_index():
    # 使用 visibility_of_all_elements_located 等待元素可见
    items = wait.until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR, '.item')))
    return items

'''封装提取内容函数'''
def scrape_detail(item):
    name = item.find_element(By.CSS_SELECTOR, 'h2').text
    categories = item.find_elements(By.CSS_SELECTOR, '.categories span')
    category = [category.text for category in categories]
    spans = item.find_elements(By.CSS_SELECTOR, '.info span')
    published_at = None
    for span in spans:
        text = span.text
        match = re.search(r'(\d{4}-\d{2}-\d{2}) 上映', text)
        if match:
            published_at = match.group(1)
            break
    score = item.find_element(By.CSS_SELECTOR, '.score').text
    return {
        'name': name,
        'category': category,
        'published_at': published_at,
        'score': score
    }
'''封装下拉操作函数'''
def pull_down():
    # 定位到第二个scroll容器
    second_scroll = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="index"]')))
    # 第二个scroll容器的下拉操作
    browser.execute_script('arguments[0].scrollTop += 1800', second_scroll)

'''封装提取单个item内电影名称的函数，用于判断是否已经被爬取'''
def get_item_title(item):
    try:
        title = item.find_element(By.CSS_SELECTOR, 'h2')  # 在 item 内部查找
        return title.text
    except NoSuchElementException:
        return None


'''封装执行方法，将以上内容串联运行'''
def main():
    # 打开网站
    browser.get(BASE_URL)
    # 第一个scroll容器的下拉操作
    browser.execute_script('window.scrollTo(0, document.body.scrollHeight);')
    # while循环，已爬取电影列表的长度不超过设置的最大爬取数时，会一直循环
    while len(scraped_titles) < MAX_NUMBER:
        # 执行爬取列表页函数
        items = scrape_index()
        # for循环，提取单个item
        for item in items:
            # 提取item中的电影名称
            title = get_item_title(item)
            # 判断title不为真或title已经存在于已爬取列表中时，继续往下执行
            if not title or title in scraped_titles:
                continue
            # 执行提取内容函数
            item_data = scrape_detail(item)
            # 将提取出的电影名称添加到已爬取列表中
            scraped_titles.append(title)
            # 日志输出
            logging.info(f'Scraping data {item_data}')
        # 再次进行判断，当前的已爬取列表长度小于最大爬取数时，执行下拉操作，等待元素可见时继续上述循环
        if len(scraped_titles) < MAX_NUMBER:
            logging.info('Scrolling down to load more items...')
            pull_down()
            wait.until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR, '.item')))  # 等待新元素加载

if __name__ == "__main__":
    main()
